package org.bouncycastle.jce.provider.asymmetric.ec;

import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPublicKey;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DSA;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.NullDigest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.jce.interfaces.ECKey;
import org.bouncycastle.jce.provider.DSABase;
import org.bouncycastle.jce.provider.DSAEncoder;
import org.bouncycastle.jce.provider.JDKKeyFactory;

public class Signature extends DSABase {
    Signature(Digest digest, DSA signer, DSAEncoder encoder) {
        super(digest, signer, encoder);
    }

    protected void engineInitVerify(PublicKey publicKey)
            throws InvalidKeyException {
        CipherParameters param;

        if (publicKey instanceof ECPublicKey) {
            param = ECUtil.generatePublicKeyParameter(publicKey);
        } else {
            try {
                byte[] bytes = publicKey.getEncoded();

                publicKey = JDKKeyFactory.createPublicKeyFromDERStream(bytes);

                if (publicKey instanceof ECPublicKey) {
                    param = ECUtil.generatePublicKeyParameter(publicKey);
                } else {
                    throw new InvalidKeyException(
                            "can't recognise key type in ECDSA based signer");
                }
            } catch (Exception e) {
                throw new InvalidKeyException(
                        "can't recognise key type in ECDSA based signer");
            }
        }

        digest.reset();
        signer.init(false, param);
    }

    protected void engineInitSign(PrivateKey privateKey, SecureRandom random)
            throws InvalidKeyException {
        CipherParameters param;

        if (privateKey instanceof ECKey) {
            param = ECUtil.generatePrivateKeyParameter(privateKey);
        } else {
            throw new InvalidKeyException(
                    "can't recognise key type in ECDSA based signer");
        }

        digest.reset();

        if (random != null) {
            signer.init(true, new ParametersWithRandom(param, random));
        } else {
            signer.init(true, param);
        }
    }

    static public class ecDSA extends Signature {
        public ecDSA() {
            super(new SHA1Digest(), new ECDSASigner(), new StdDSAEncoder());
        }
    }

    static public class ecDSAnone extends Signature {
        public ecDSAnone() {
            super(new NullDigest(), new ECDSASigner(), new StdDSAEncoder());
        }
    }

    // BEGIN android-removed
    // static public class ecDSA224
    // extends Signature
    // {
    // public ecDSA224()
    // {
    // super(new SHA224Digest(), new ECDSASigner(), new StdDSAEncoder());
    // }
    // }
    // END android-removed

    static public class ecDSA256 extends Signature {
        public ecDSA256() {
            super(new SHA256Digest(), new ECDSASigner(), new StdDSAEncoder());
        }
    }

    static public class ecDSA384 extends Signature {
        public ecDSA384() {
            super(new SHA384Digest(), new ECDSASigner(), new StdDSAEncoder());
        }
    }

    static public class ecDSA512 extends Signature {
        public ecDSA512() {
            super(new SHA512Digest(), new ECDSASigner(), new StdDSAEncoder());
        }
    }

    // BEGIN android-removed
    // static public class ecDSARipeMD160
    // extends Signature
    // {
    // public ecDSARipeMD160()
    // {
    // super(new RIPEMD160Digest(), new ECDSASigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecNR
    // extends Signature
    // {
    // public ecNR()
    // {
    // super(new SHA1Digest(), new ECNRSigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecNR224
    // extends Signature
    // {
    // public ecNR224()
    // {
    // super(new SHA224Digest(), new ECNRSigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecNR256
    // extends Signature
    // {
    // public ecNR256()
    // {
    // super(new SHA256Digest(), new ECNRSigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecNR384
    // extends Signature
    // {
    // public ecNR384()
    // {
    // super(new SHA384Digest(), new ECNRSigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecNR512
    // extends Signature
    // {
    // public ecNR512()
    // {
    // super(new SHA512Digest(), new ECNRSigner(), new StdDSAEncoder());
    // }
    // }
    //
    // static public class ecCVCDSA
    // extends Signature
    // {
    // public ecCVCDSA()
    // {
    // super(new SHA1Digest(), new ECDSASigner(), new CVCDSAEncoder());
    // }
    // }
    //
    // static public class ecCVCDSA224
    // extends Signature
    // {
    // public ecCVCDSA224()
    // {
    // super(new SHA224Digest(), new ECDSASigner(), new CVCDSAEncoder());
    // }
    // }
    //
    // static public class ecCVCDSA256
    // extends Signature
    // {
    // public ecCVCDSA256()
    // {
    // super(new SHA256Digest(), new ECDSASigner(), new CVCDSAEncoder());
    // }
    // }
    // END android-removed

    private static class StdDSAEncoder implements DSAEncoder {
        public byte[] encode(BigInteger r, BigInteger s) throws IOException {
            ASN1EncodableVector v = new ASN1EncodableVector();

            v.add(new DERInteger(r));
            v.add(new DERInteger(s));

            return new DERSequence(v).getEncoded(ASN1Encodable.DER);
        }

        public BigInteger[] decode(byte[] encoding) throws IOException {
            ASN1Sequence s = (ASN1Sequence) ASN1Object.fromByteArray(encoding);
            BigInteger[] sig = new BigInteger[2];

            sig[0] = ((DERInteger) s.getObjectAt(0)).getValue();
            sig[1] = ((DERInteger) s.getObjectAt(1)).getValue();

            return sig;
        }
    }

    @SuppressWarnings("unused")
    private static class CVCDSAEncoder implements DSAEncoder {
        public byte[] encode(BigInteger r, BigInteger s) throws IOException {
            byte[] first = makeUnsigned(r);
            byte[] second = makeUnsigned(s);
            byte[] res;

            if (first.length > second.length) {
                res = new byte[first.length * 2];
            } else {
                res = new byte[second.length * 2];
            }

            System.arraycopy(first, 0, res, res.length / 2 - first.length,
                    first.length);
            System.arraycopy(second, 0, res, res.length - second.length,
                    second.length);

            return res;
        }

        private byte[] makeUnsigned(BigInteger val) {
            byte[] res = val.toByteArray();

            if (res[0] == 0) {
                byte[] tmp = new byte[res.length - 1];

                System.arraycopy(res, 1, tmp, 0, tmp.length);

                return tmp;
            }

            return res;
        }

        public BigInteger[] decode(byte[] encoding) throws IOException {
            BigInteger[] sig = new BigInteger[2];

            byte[] first = new byte[encoding.length / 2];
            byte[] second = new byte[encoding.length / 2];

            System.arraycopy(encoding, 0, first, 0, first.length);
            System.arraycopy(encoding, first.length, second, 0, second.length);

            sig[0] = new BigInteger(1, first);
            sig[1] = new BigInteger(1, second);

            return sig;
        }
    }
}
